/* Copyright (C) 2009 Vlad Roubtsov. All rights reserved. */

#include "vr/config.hpp"

#if defined (VR_USE_LONGJMP)
#   include <csetjmp>
#endif
#include <cstdio> // use C I/O for extra robustness in certain places
#include <cstring> // strncpy
#include <typeinfo>

#include <boost/unordered_set.hpp>
using boost::unordered_set;

#include "vr/deject/factory/component_factory.hpp"
#include "vr/exception/exceptions.hpp"
#include "vr/util/assertions.hpp"
#include "vr/util/logger.hpp"

#include "vr/deject/container.hpp"

using namespace vr::deject;
using namespace vr::deject::factory;
using namespace vr::utility;

//----------------------------------------------------------------------------

/*
 * The full list of dependencies for a component can contain duplicates. This method
 * will calculate a flat list of immediate dependencies with unique IDs.
 */
static void
unique_dependencies (const component_desc & cd, unordered_set<component_id_type> & out)
{
    const ptr_vector<dependency_desc> & deps = cd.m_deps;
    for (ptr_vector<dependency_desc>::size_type i = 0, i_limit = deps.size (); i < i_limit; ++ i)
    {
        const dependency_desc & dd = deps [i];
        switch (dd.m_type)
        {
            case dep_value_type::REFERENCE:
            {
                out.insert (static_cast<const component_id_type &> (dd));
            }
            break;

            case dep_value_type::SEQ_OF_REFERENCE:
            {
                const dependency_desc::seq_of_dep_id_type & child_deps = static_cast<const dependency_desc::seq_of_dep_id_type &> (dd);
                for (dependency_desc::seq_of_dep_id_type::size_type k = 0, k_limit = child_deps.size (); k < k_limit; ++ k)
                {
                    out.insert (child_deps [k]);
                }
            }
            break;

            case dep_value_type::VALUE:
            case dep_value_type::MAP_OF_VALUE:
            {
                // [skip values]
            }
            break;

            default: VR_SFAIL ("invalid enum value")

        } // end of switch
    }
}

#define VR_STRNCPY(src, dest, n) std::strncpy (dest, src, n - 1); dest [n - 1] = '\0';

const int NAME_BUF_LEN = 256;

#if defined (VR_USE_LONGJMP)

typedef void ( * terminate_handler_t ) ();

static std::jmp_buf jctx; // TODO thread safety
static volatile bool in_terminate_handler = false;

static string::value_type ID [NAME_BUF_LEN];
static string::value_type implID [NAME_BUF_LEN];
static string::value_type x_msg_buf [NAME_BUF_LEN];


static void
terminate_handler ()
{
    // try to capture any currently pending exception;
    // if no std::exception is pending, this handler is re-entered
    // right away and will skip the same capture attempt:

    if (! in_terminate_handler)
    {
        in_terminate_handler = true;

        try
        {
            throw; // re-throw
        }
        catch (std::exception & e)
        {
            VR_STRNCPY (e.what (), x_msg_buf, NAME_BUF_LEN)
        }
        catch (...)
        {
            // [can't do anything in this case]
        }
    }

    in_terminate_handler = false;

    longjmp (jctx, 1);
}

#endif // VR_USE_LONGJMP

//----------------------------------------------------------------------------

auto_ptr<container>
container::create (const string & name, const cfg_type & cfg)
{
    // (1) sort components topologically (also detect dangling component IDs):

    component_map mapping;
    vector<ctx *> ordering;

    for (cfg_type::const_iterator c = cfg.begin (), c_limit = cfg.end (); c != c_limit; ++ c)
    {
        traverse (c->first, cfg, mapping, ordering);
    }

    // ['ordering' contains topological order (dependencies before dependents)]

    // (2) instantiate components (which is expected to be inexpensive: mostly injection logic):

    for (vector<ctx *>::size_type o = 0, o_limit = ordering.size (); o != o_limit; ++ o)
    {
        ctx & node_ctx = * (ordering [o]);

        ptr_vector<dependency> args;
        bind_dependencies (node_ctx, mapping, args);
        LOG_DEBUG ("bound " << args.size () << " deps for " << node_ctx.m_descriptor->m_ID);

        const component_desc & descriptor = * node_ctx.m_descriptor;
        node_ctx.m_component = component_factory::create (descriptor.m_implID, descriptor.m_ID, args);

        // memorize "is instance of" for 'initializable' and 'startable':

        node_ctx.m_initializable = dynamic_cast<initializable *> (node_ctx.m_component); // can be 0
        node_ctx.m_startable = dynamic_cast<startable *> (node_ctx.m_component); // can be 0

        VR_SASSERT (node_ctx.m_component);
        LOG_INFO ("[" << descriptor.m_ID << "] mapped to an instance of [" << typeid (* node_ctx.m_component).name () << "] @" << node_ctx.m_component);

        node_ctx.m_ID = descriptor.m_ID;
        node_ctx.m_implID = descriptor.m_implID;
        node_ctx.m_descriptor = 0; // invalidate descriptor pointer [to catch possible invalid references later]
    }

    if (mapping.empty ())
    {
        VR_SASSERT (ordering.empty ());
        LOG_WARN ("container [" << name << "] is empty");
    }

    // [remaining lifecycle events are implemented in start()/stop()]

    return auto_ptr<container> (new container (name, mapping, ordering));
}
//............................................................................

container::container (const string & name, container::component_map & components, const vector<container::ctx *> & ordering) :
    m_name (name),
    m_components (),
    m_ordering (ordering)
{
    m_components.transfer (components);
    VR_SASSERT (m_components.size () == m_ordering.size ());

    // TODO container's own state tracking
}

container::~container ()
{
    stop (); // clean up [idempotent call]
}
//............................................................................
//............................................................................

void
container::start ()
{
    const int_fast32_t ccount = m_ordering.size ();

    for (int_fast32_t o = 0; o < ccount; ++ o)
    {
        ctx & node_ctx = * (m_ordering [o]);

        if (node_ctx.m_initializable)
        {
            LOG_INFO ("initializing component [" + node_ctx.m_ID + ": " + node_ctx.m_implID + "] ...");

            try
            {
                node_ctx.m_initializable->initialize ();
            }
            catch (std::exception & e)
            {
                stop (); // clean up [idempotent call]

                // abort container init:

                __VR_THROW_X (std::runtime_error, "component [" + node_ctx.m_ID + ": " + node_ctx.m_implID + "] failed to initialize: " + e.what ());
            }
            catch (...)
            {
                stop (); // clean up [idempotent call]

                // abort container init:

                __VR_THROW_X (std::runtime_error, "component [" + node_ctx.m_ID + ": " + node_ctx.m_implID + "] failed to initialize: unknown exception");
            }
        }
    }

    for (int_fast32_t o = 0; o < ccount; ++ o)
    {
        ctx & node_ctx = * (m_ordering [o]);

        if (node_ctx.m_startable)
        {
            LOG_INFO ("starting component [" + node_ctx.m_ID + ": " + node_ctx.m_implID + "] ...");

            try
            {
                node_ctx.m_startable->start ();
            }
            catch (std::exception & e)
            {
                stop (); // clean up [idempotent call]

                // abort container init:

                __VR_THROW_X (std::runtime_error, "component [" + node_ctx.m_ID + ": " + node_ctx.m_implID + "] failed to start: " + e.what ());
            }
            catch (...)
            {
                stop (); // clean up [idempotent call]

                // abort container init:

                __VR_THROW_X (std::runtime_error, "component [" + node_ctx.m_ID + ": " + node_ctx.m_implID + "] failed to start: unknown exception");
            }
        }
    }
}

void
container::stop () throw ()
{
    // TODO need a class-scoped mutex here

    const int_fast32_t ccount = m_ordering.size ();

    // stop startable components:

#if defined (VR_USE_LONGJMP)

    x_msg_buf [NAME_BUF_LEN - 1] = '\0'; // reset buffer to an empty string

    terminate_handler_t current_handler = std::set_terminate (terminate_handler);

    volatile int_fast32_t o = ccount;

#else // VR_USE_LONGJMP

    int_fast32_t o = ccount;

#endif // VR_USE_LONGJMP

    while (-- o >= 0) // in reverse init order
    {
        ctx & node_ctx = * (m_ordering [o]);

        VR_STRNCPY (node_ctx.m_ID.c_str (), ID, NAME_BUF_LEN)
        VR_STRNCPY (node_ctx.m_implID.c_str (), implID, NAME_BUF_LEN)

#if defined (VR_USE_LONGJMP)

        if (setjmp (jctx)) // setjmp() is required to be a macro
        {
            std::printf ("[%s]: component [%s: %s] failed to stop: %s\n", __VR_LOCATION, ID, implID, (x_msg_buf [0] ? x_msg_buf : "unknown exception"));

            continue; // complete the loop to give other components a chance to clean up
        }

#endif // VR_USE_LONGJMP

        if (node_ctx.m_startable)
        {
            std::printf ("[%s]: stopping component [%s: %s] ...\n", __VR_LOCATION, ID, implID);

            try
            {
                node_ctx.m_startable->stop (); // note that start() may not have been called for this component (due to init process abort)
            }
            catch (std::exception & e)
            {
                VR_STRNCPY (e.what (), x_msg_buf, NAME_BUF_LEN)

                std::printf ("[%s]: component [%s: %s] failed to stop: %s\n", __VR_LOCATION, ID, implID, (x_msg_buf [0] ? x_msg_buf : "unknown exception"));
            }
            catch (...)
            {
                std::printf ("[%s]: component [%s: %s] failed to stop: unknown exception\n", __VR_LOCATION, ID, implID);
            }
        }
    }

#if defined (VR_USE_LONGJMP)

    std::set_terminate (current_handler);

#endif // VR_USE_LONGJMP

    // GC components:

    for (int_fast32_t o = ccount; -- o >= 0; ) // in reverse init order
    {
        m_components.erase (m_ordering [o]->m_ID); // call component destructor
    }
    VR_SASSERT (m_components.empty ());

    m_ordering.clear ();
}
//............................................................................
//............................................................................

void
container::traverse (const component_id_type & node, const cfg_type & cfg, container::component_map & mapping, vector<container::ctx *> & ordering)
{
    ctx & node_ctx = mapping [node]; // insert if not present

    switch (node_ctx.m_visit_state)
    {
        case 0:
        {
            const cfg_type::const_iterator ci = cfg.find (node);
            if (ci == cfg.end ())
                __VR_THROW_X (std::domain_error, "no assembly mapping defined for [" + node + "]");

            const component_desc & node_descriptor = * (ci->second);
            node_ctx.m_descriptor = & node_descriptor; // remember the cfg descriptor for this node

            unordered_set<component_id_type> node_children;
            unique_dependencies (node_descriptor, node_children); // populate 'node_children'

            node_ctx.m_visit_state = 1; // visit started

            LOG_DEBUG ("visit [" << node << "]: start");

            for (unordered_set<component_id_type>::const_iterator child = node_children.begin (), child_limit = node_children.end (); child != child_limit; ++ child)
            {
                traverse (* child, cfg, mapping, ordering); // recurse
            }
        }
        break;

        case 1:
        {
            __VR_THROW_X (std::domain_error, "dependency cycle detected involving component [" + node + "]");
        }
        // break;

        default: return; // already visited

    } // end of switch

    node_ctx.m_visit_state = 2; // visit finished
    LOG_DEBUG ("visit [" << node << "]: finish");

    ordering.push_back (& node_ctx); // place 'node' after its dependencies
}
//............................................................................

void
container::bind_dependencies (const ctx & node_ctx, const container::component_map & mapping, ptr_vector<dependency> & out)
{
    VR_SASSERT (node_ctx.m_descriptor);
    const component_desc & descriptor = * node_ctx.m_descriptor;

    const ptr_vector<dependency_desc> & deps = descriptor.m_deps;
    for (ptr_vector<dependency_desc>::size_type i = 0, i_limit = deps.size (); i < i_limit; ++ i)
    {
        const dependency_desc & dd = deps [i];
        switch (dd.m_type)
        {
            case dep_value_type::REFERENCE:
            {
                const component_id_type & dep_node = static_cast<const component_id_type &> (dd);

                component_map::const_iterator i = mapping.find (dep_node);
                VR_SASSERT (i != mapping.end ());

                const ctx & dep_node_ctx = * i->second;
                VR_SASSERT (dep_node_ctx.m_component); // dependency should have been instantiated already

                out.push_back (new dependency (dep_node_ctx.m_component));
            }
            break;

            case dep_value_type::SEQ_OF_REFERENCE:
            {
                const dependency_desc::seq_of_dep_id_type & child_deps = static_cast<const dependency_desc::seq_of_dep_id_type &> (dd);
                for (dependency_desc::seq_of_dep_id_type::size_type k = 0, k_limit = child_deps.size (); k < k_limit; ++ k)
                {
                    const component_id_type & dep_node = child_deps [k];

                    component_map::const_iterator i = mapping.find (dep_node);
                    VR_SASSERT (i != mapping.end ());

                    const ctx & dep_node_ctx = * i->second;
                    VR_SASSERT (dep_node_ctx.m_component); // dependency should have been instantiated already

                    out.push_back (new dependency (dep_node_ctx.m_component));
                }
            }
            break;

            case dep_value_type::VALUE:
            {
                out.push_back (new dependency (static_cast<const value &> (dd)));
            }
            break;

            case dep_value_type::MAP_OF_VALUE:
            {
                out.push_back (new dependency (static_cast<const value::map_of_val_type &> (dd)));
            }
            break;

            default: VR_SFAIL ("invalid enum value")

        } // end of switch
    }
}
//............................................................................

bool
container::destroy (const component_id_type & c_ID, const string & c_implID, component_base * const c)
{
    // TODO thread safety around static state

    VR_STRNCPY (c_ID.c_str (), ID, NAME_BUF_LEN)
    VR_STRNCPY (c_implID.c_str (), implID, NAME_BUF_LEN)

    volatile bool rc = false;

    volatile terminate_handler_t current_handler = std::set_terminate (terminate_handler);
    {
        if (setjmp (jctx)) // [setjmp() is required to be a macro]
        {
            // if we weren't able to delete 'c', we might as well give up the associated heap memory
            // and complete this method call in order to give the other component destructors a chance
            // to succeed:

            std::printf ("[%s]: failed to properly destroy component [%s: %s]: %s\n", __VR_LOCATION, ID, implID, (x_msg_buf [0] ? x_msg_buf : "unknown exception"));
        }
        else
        {
            std::printf ("[%s]: destroying component [%s: %s] ...\n", __VR_LOCATION, ID, implID);

            try
            {
                delete c;

                rc = true;
            }
            catch (std::exception & e)
            {
                VR_STRNCPY (e.what (), x_msg_buf, NAME_BUF_LEN)

                std::printf ("[%s]: failed to properly destroy component [%s: %s]: %s\n", __VR_LOCATION, ID, implID, (x_msg_buf [0] ? x_msg_buf : "unknown exception"));
            }
            catch (...)
            {
                std::printf ("[%s]: failed to properly destroy component [%s: %s]: unknown exception\n", __VR_LOCATION, ID, implID);
            }
        }
    }
    std::set_terminate (current_handler);

    return rc;
}
//............................................................................
//............................................................................

container::ctx::~ctx ()
{
    if (! m_component) return; // guarantee no double-destruction

    component_base * const component = m_component;
    m_component = 0;

    destroy (m_ID, m_implID, component);
}

#undef VR_STRNCPY

//----------------------------------------------------------------------------
